{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 205 Agent custom"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在agent中，我们是如何实现自己定义的，带着这个问题我们自己定义一个智能体，我们将使用 OpenAI 函数调用来创建此代理。我们将首先在没有内存的情况下创建它，但随后我们将展示如何添加内存。需要内存才能启用对话。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "#设置代理\n",
    "import os\n",
    "\n",
    "os.environ['http_proxy'] = 'http://127.0.0.1:10809'\n",
    "os.environ['https_proxy'] = 'http://127.0.0.1:10809'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## LLM加载"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 首先，让我们加载将用于控制代理的语言模型。\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义工具"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 接下来，让我们定义一些要使用的工具。让我们编写一个非常简单的 Python 函数来计算传入的单词的长度。\n",
    "from langchain.agents import tool\n",
    "\n",
    "\n",
    "@tool\n",
    "def get_word_length(word: str) -> int:\n",
    "    \"\"\"Returns the length of a word.\"\"\"\n",
    "    return len(word)\n",
    "\n",
    "\n",
    "tools = [get_word_length]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 创建提示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are very powerful assistant, but don't know current events\",\n",
    "        ),\n",
    "        (\"user\", \"{input}\"),\n",
    "        MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 将工具绑定到LLM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 代理如何知道它可以使用哪些工具？在本例中，我们依赖于 OpenAI 函数调用 LLM，它将函数作为单独的参数，并经过专门训练，可以知道何时调用这些函数。\n",
    "# 要将我们的工具传递给代理，我们只需要将它们格式化为 OpenAI 函数格式并将它们传递给我们的模型。（通过 bind 对函数进行绑定操作，我们确保每次调用模型时都会传入它们。\n",
    "from langchain_community.tools.convert_to_openai import format_tool_to_openai_function\n",
    "\n",
    "llm_with_tools = llm.bind(functions=[format_tool_to_openai_function(t) for t in tools])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 创建代理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将这些部分放在一起，我们现在可以创建代理。我们将导入最后两个实用程序函数：一个组件用于格式化中间步骤（代理操作、工具输出对）以输入可发送到模型的消息，以及一个用于将输出消息转换为代理操作/代理完成的组件。\n",
    "from langchain.agents.format_scratchpad import format_to_openai_function_messages\n",
    "from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser\n",
    "\n",
    "agent = (\n",
    "    {\n",
    "        \"input\": lambda x: x[\"input\"],\n",
    "        \"agent_scratchpad\": lambda x: format_to_openai_function_messages(\n",
    "            x[\"intermediate_steps\"]\n",
    "        ),\n",
    "    }\n",
    "    | prompt\n",
    "    | llm_with_tools\n",
    "    | OpenAIFunctionsAgentOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import AgentExecutor\n",
    "\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_word_length` with `{'word': 'educa'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m5\u001b[0m\u001b[32;1m\u001b[1;3mThere are 5 letters in the word \"educa\".\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': 'How many letters in the word educa',\n",
       " 'output': 'There are 5 letters in the word \"educa\".'}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent_executor.invoke({\"input\": \"How many letters in the word educa\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='There are 6 letters in the word \"educa\".')"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 如果我们将其与基础LLM进行比较\n",
    "llm.invoke(\"How many letters in the word educa\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 添加内存\n",
    "\n",
    "我们有一个代理！但是，此代理是无状态的 - 它不记得有关先前交互的任何内容。这意味着你不能轻易提出后续问题。让我们通过添加内存来解决这个问题。\n",
    "\n",
    "为了做到这一点，我们需要做两件事：\n",
    "\n",
    "- 在提示中为内存变量添加一个位置\n",
    "\n",
    "- 跟踪聊天记录"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 首先，让我们在提示中添加一个内存位置。为此，我们为带有键 \"chat_history\" 的消息添加一个占位符。请注意，我们将其放在新用户输入的上方（以遵循对话流）\n",
    "from langchain.prompts import MessagesPlaceholder\n",
    "\n",
    "MEMORY_KEY = \"chat_history\"\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are very powerful assistant, but bad at calculating lengths of words.\",\n",
    "        ),\n",
    "        MessagesPlaceholder(variable_name=MEMORY_KEY),\n",
    "        (\"user\", \"{input}\"),\n",
    "        MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 然后，我们可以设置一个列表来跟踪聊天记录\n",
    "from langchain_core.messages import AIMessage, HumanMessage\n",
    "\n",
    "chat_history = []"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 然后我们可以把它们放在一起！\n",
    "agent = (\n",
    "    {\n",
    "        \"input\": lambda x: x[\"input\"],\n",
    "        \"agent_scratchpad\": lambda x: format_to_openai_function_messages(\n",
    "            x[\"intermediate_steps\"]\n",
    "        ),\n",
    "        \"chat_history\": lambda x: x[\"chat_history\"],\n",
    "    }\n",
    "    | prompt\n",
    "    | llm_with_tools\n",
    "    | OpenAIFunctionsAgentOutputParser()\n",
    ")\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_word_length` with `{'word': 'educa'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m5\u001b[0m\u001b[32;1m\u001b[1;3mThere are 5 letters in the word \"educa\".\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3mNo, \"educa\" is not a real word in English.\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': 'is that a real word?',\n",
       " 'chat_history': [HumanMessage(content='how many letters in the word educa?'),\n",
       "  AIMessage(content='There are 5 letters in the word \"educa\".')],\n",
       " 'output': 'No, \"educa\" is not a real word in English.'}"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 运行时，我们现在需要将输入和输出作为聊天记录进行跟踪\n",
    "input1 = \"how many letters in the word educa?\"\n",
    "result = agent_executor.invoke({\"input\": input1, \"chat_history\": chat_history})\n",
    "chat_history.extend(\n",
    "    [\n",
    "        HumanMessage(content=input1),\n",
    "        AIMessage(content=result[\"output\"]),\n",
    "    ]\n",
    ")\n",
    "agent_executor.invoke({\"input\": \"is that a real word?\", \"chat_history\": chat_history})"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
